/*
 *
 *  *    Copyright 2020-2021 luter.me
 *  *
 *  *    Licensed under the Apache License, Version 2.0 (the "License");
 *  *    you may not use this file except in compliance with the License.
 *  *    You may obtain a copy of the License at
 *  *
 *  *      http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  *    Unless required by applicable law or agreed to in writing, software
 *  *    distributed under the License is distributed on an "AS IS" BASIS,
 *  *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  *    See the License for the specific language governing permissions and
 *  *    limitations under the License.
 *
 */

package com.luter.heimdall.core.manager;
 
import com.luter.heimdall.core.authorization.authority.GrantedAuthority;
import com.luter.heimdall.core.authorization.authority.MethodAndUrlGrantedAuthority;
import com.luter.heimdall.core.authorization.authority.SimpleGrantedAuthority;
import com.luter.heimdall.core.authorization.provider.AuthorityDataProvider;
import com.luter.heimdall.core.authorization.store.AuthorizationStore;
import com.luter.heimdall.core.details.UserDetails;
import com.luter.heimdall.core.exception.HeimdallUnauthorizedException;
import com.luter.heimdall.core.listener.AbstractAuthEvent;
import com.luter.heimdall.core.utils.HttpMethodEnum;
import com.luter.heimdall.core.utils.PathUtil;
import com.luter.heimdall.core.utils.StrUtils;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;
import org.slf4j.Logger;

import java.util.*;

import static org.slf4j.LoggerFactory.getLogger;

/**
 * 资源路由授权管理器
 * <p>
 * 1、获取系统权限拦截规则
 * <p>
 * 2、获取request的请求Url，通过AntPathMatcher与系统权限拦截规则中的Url进行匹配，匹配上，进入授权，不匹配则放行
 * <p>
 * 3、获取到当前登录用户具有的权限，遍历授权
 * <p>
 * 4、判断用户权限类型，如果是：MethodAndUrlGrantedAuthority进入restful授权，如果是SimpleGrantedAuthority，进入普通精确Url授权
 * <p>
 * 4.1 MethodAndUrlGrantedAuthority 授权，遍历用户权限，确定具备此url的权限，然后拿到method，如果 Method 与当前request 的 method 一致
 * <p>
 * 或者 method ="all",授权通过.
 * <p>
 * 4.2 SimpleGrantedAuthority授权，遍历用户权限，查找与所需perm匹配的用户权限，如果有，授权通过
 *
 * @author luter
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true, fluent = true)
public class HeimdallAuthorizationManager extends AbstractAuthEvent implements AuthorizationManager {
    /**
     * The constant log.
     */
    private static final transient Logger log = getLogger(HeimdallAuthorizationManager.class);

    /**
     * The constant PATH_UTIL.
     */
    private static final PathUtil PATH_UTIL = new PathUtil();
    /**
     * The Authorization store.
     */
    private AuthorizationStore authorizationStore;

    /**
     * The Authorization manager.
     */
    private AuthenticationManager authenticationManager;

    /**
     * 构造 授权 管理器
     * <p>
     * 1、获取系统权限拦截规则
     * <p>
     * 2、获取request的请求Url，通过AntPathMatcher与系统权限拦截规则中的Url进行匹配，匹配上，进入授权，不匹配则放行
     * <p>
     * 3、获取到当前登录用户具有的权限，遍历授权
     * <p>
     * 4、判断用户权限类型，如果是：MethodAndUrlGrantedAuthority进入restful授权，如果是SimpleGrantedAuthority，进入普通精确Url授权
     * <p>
     * 4.1 MethodAndUrlGrantedAuthority 授权，遍历用户权限，确定具备此url的权限，然后拿到method，如果 Method 与当前request 的 method 一致
     * <p>
     * 或者 method ="all",授权通过.
     * <p>
     * 4.2 SimpleGrantedAuthority授权，遍历用户权限，查找与所需perm匹配的用户权限，如果有，授权通过
     *
     * @param authorizationStore    权限 Store
     * @param authenticationManager 认证管理器
     */
    public HeimdallAuthorizationManager(AuthorizationStore authorizationStore,
                                        AuthenticationManager authenticationManager) {
        if (null == authorizationStore) {
            throw new IllegalArgumentException("AuthorizationStore must not be null");
        }
        if (null == authenticationManager) {
            throw new IllegalArgumentException("AuthenticationManager must not be null");
        }
        this.authorizationStore = authorizationStore;
        this.authenticationManager = authenticationManager;
    }

    @Override
    public boolean isAuthorized(String method, String url, boolean isThrowEx) {
        log.debug("[isAuthorized]::method = [{}], url = [{}], isThrowEx = [{}]", method, url, isThrowEx);
        final UserDetails user = authenticationManager.getCurrentUser(isThrowEx);
        return isAuthorized(user, method, url, isThrowEx);
    }

    /**
     * 授权判断
     *
     * @param userDetails 用户详情
     * @param method      请求方法
     * @param url         请求 url
     * @param isThrowEx   是否抛出异常
     * @return the boolean
     * @see HttpMethodEnum#isValidMethod(String)
     * @see PathUtil#isValidPath(String)
     * @see AuthorizationStore
     * @see AuthorityDataProvider
     */
    @Override
    public boolean isAuthorized(UserDetails userDetails, String method, String url, boolean isThrowEx) {
        //拿到应用权限
        Map<String, List<String>> appAuthorities = getAuthorizationStore().getAppAuthorities(userDetails);
        //没拿到，或者拿到的是空的，全部放行并提示
        if (null == appAuthorities || appAuthorities.isEmpty()) {
            log.error("[isAuthorized]:: ATTENTION==> The Authorities of this app [{}] is empty.and all of resources access will be passed。" +
                            "userDetails = [{}], method = [{}], url = [{}], isThrowEx = [{}]",
                    userDetails.getAppId(), userDetails, method, url, isThrowEx);
            onResourceAuthorized(ResourceAuthorizedResultType.APP_RULE_EMPTY, userDetails, method, url, isThrowEx);
            return true;
        }
        //应用权限不为空，获取用户权限
        final List<? extends GrantedAuthority> userAuthorities = getAuthorizationStore().getUserAuthorities(userDetails);
        //这个人啥权限也没有，直接拒绝并返回
        if (null == userAuthorities || userAuthorities.isEmpty()) {
            onResourceAuthorized(ResourceAuthorizedResultType.USER_RULE_EMPTY, userDetails, method, url, isThrowEx);
            if (isThrowEx) {
                throw new HeimdallUnauthorizedException("You are not authorized to access the resource: [" +
                        method + StrUtils.COLON + url + "] , Access is denied.");
            }
            return false;
        }
        //遍历 应用权限
        for (Map.Entry<String, List<String>> entry : appAuthorities.entrySet()) {
            //要拦截的url
            final String filterUrl = entry.getKey();
            //需要的perm授权标识，MethodAndUrlGrantedAuthority模式下无效
            final List<String> filterPerm = entry.getValue();
            //访问的 url 与 过滤的 url 匹配上，开始判断是否具有权限
            if (PATH_UTIL.match(filterUrl, url)) {
                //遍历用户的权限
                for (GrantedAuthority userAuthority : userAuthorities) {
                    //这是 restful 授权规则，也就是 Method:Url 模式的
                    if (userAuthority instanceof MethodAndUrlGrantedAuthority) {
                        MethodAndUrlGrantedAuthority mga = (MethodAndUrlGrantedAuthority) userAuthority;
                        //鉴权规则的 url 与当前访问的 url 匹配
                        if (PATH_UTIL.match(mga.getUrl(), url)) {
                            //鉴权规则的的请求方法与当前请求的 method 匹配，鉴权规则的的请求方法 = ALL_METHOD_NAME,则鉴权通过
                            if (method.equalsIgnoreCase(mga.getMethod()) || ALL_METHOD_NAME.equalsIgnoreCase(mga.getMethod())) {
                                log.info("[isAuthorized|MethodAndUrlGrantedAuthority]:: Access permitted. userDetails = [{}]," +
                                                "  method = [{}], url = [{}], isThrowEx = [{}]",
                                        userDetails, method, url, isThrowEx);
                                onResourceAuthorized(ResourceAuthorizedResultType.ACCEPTED, userDetails, method, url, isThrowEx);
                                return true;
                            }
                        }
                    }
                    ////普通 url 鉴权规则，也就是标识符类型的
                    else if (userAuthority instanceof SimpleGrantedAuthority) {
                        SimpleGrantedAuthority mga = (SimpleGrantedAuthority) userAuthority;
                        //匹配规则里有规则，并且包含用户的，有权限
                        if (filterPerm.size() > 0 && filterPerm.contains(mga.getAuthority())) {
                            log.info("[isAuthorized|SimpleGrantedAuthority]:: Access permitted. " +
                                            "userDetails = [{}],  method = [{}], url = [{}], isThrowEx = [{}]",
                                    userDetails, method, url, isThrowEx);
                            onResourceAuthorized(ResourceAuthorizedResultType.ACCEPTED, userDetails, method, url, isThrowEx);
                            return true;
                        }
                    } else {
                        //传进来的不是GrantedAuthority的实现类，没法处理.不通过
                        log.error("[isAuthorized]::Unsupported authorization grant type. All requests will be passed by default. " +
                                        "userDetails = [{}],   method = [{}], url = [{}], isThrowEx = [{}]",
                                userDetails, method, url, isThrowEx);
                    }
                }
            }
        }
        onResourceAuthorized(ResourceAuthorizedResultType.DENIED, userDetails, method, url, isThrowEx);
        //否则授权不通过
        if (isThrowEx) {
            throw new HeimdallUnauthorizedException("You are not authorized to access the resource: [" +
                    method + StrUtils.COLON + url + "] , Access is denied.");
        }
        return false;
    }

    @Override
    public AuthorizationStore getAuthorizationStore() {
        return authorizationStore;
    }

    @Override
    public AuthenticationManager getAuthenticationManager() {
        return authenticationManager;
    }

}


